//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: Expression.cs
//
//  Description: Base class for general expressions.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using Chili.Opf3.Storages;
using Chili.Opf3.Query.OPathParser;
using Chili.Opf3.Mapping;

namespace Chili.Opf3.Query
{
	/// <summary>
	/// Class that represents the base class for all expressions used in the framework.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class represents the base class for all expressions used in the framework. <see cref="ObjectExpression">ObjectExpression</see>
	/// and <see cref="PersistentObjectExpression">PersistentObjectExpression</see> inherit from it. It provides basic functionality such as
	/// converting the expression to a storage dependent <see cref="CompiledQuery">CompiledQuery</see> class.
	/// </para>
	/// <para>
	/// <see cref="Expression">Expression</see> provides static methods that create a expression to persist a persistent or to reload a persistent
	/// from the database.
	/// </para>
	/// </remarks>
	[Serializable]
	public abstract class Expression : IQuery
	{
		private QueryTypes _queryType = QueryTypes.Unknown;
		private int _timeout = 30;

		/// <summary>
		/// Creates an instance of the <see cref="Expression">Expression Class</see>.
		/// </summary>
		/// <param name="queryType">The type of the query.</param>
		internal Expression(QueryTypes queryType)
		{
			_queryType = queryType;
		}

		/// <summary>
		/// Creates and returns an <see cref="Expression">Expression</see> that is used to do a persist of a persistent object.
		/// </summary>
		/// <param name="obj">The object that is going to be persisted.</param>
		/// <param name="context">The <see cref="Chili.Opf3.ObjectContext">ObjectContext</see> used to persist the object.</param>
		public static Expression CreatePersistExpression(object obj, ObjectContext context)
		{
			if (obj == null)
				throw new ArgumentNullException("obj");
			
			// Check the type of query that needs to be created.
			PersistentOperations po = context.ObjectPersister.GetCurrentPersistentOperation(obj);
			switch (po)
			{
				case PersistentOperations.Delete:
					return new PersistentObjectExpression(QueryTypes.Delete, obj);
				case PersistentOperations.Insert:
					return new PersistentObjectExpression(QueryTypes.Insert, obj);
				case PersistentOperations.Update:
					return new PersistentObjectExpression(QueryTypes.Update, obj);
				default:
					throw new NotSupportedException("PersistentOperation not supported by the method.");
			}
		}

		/// <summary>
		/// Creates and returns an <see cref="Expression">Expression</see> that is used to load an instance of the given
		/// persistent object.
		/// </summary>
		/// <param name="obj">The object that is going to be loaded.</param>
		public static Expression CreateLoadExpression(object obj)
		{
			if (obj == null)
				throw new ArgumentNullException("obj");

			TypeMapping typeMapping = TypeMapping.GetTypeMapping(obj);

			// Get the persistent attribute for the type and create an ObjectExpression.
			PersistentAttribute attribute = typeMapping.PersistentAttribute;
			ObjectExpression objectExpression = new ObjectExpression(QueryTypes.Load, typeMapping.PersistentType, attribute);
			objectExpression.Items.Add(new OEWhereCondition());

			// Get the base item.
			OEItem baseItem = objectExpression.Items[0];

			// Get the conditions for the select query.
			foreach (IMemberInfo info in typeMapping.Members.GetIdentifierMemberMappings())
			{
				// Get the value of the member.
				object value = info.GetValue(obj);

				// Create a condition that is added.
				OECondition condition = new OECondition();
				condition.Items.Add(new OEMember(info.Name, info.FieldAttribute, attribute));
				condition.Items.Add(new OEComparer("="));
				condition.Items.Add(new OEParameter(value, info.MemberType));

				if (baseItem.Items.Count > 0)
					baseItem.Items.Add(new OEConcat("And"));

				// Add the condition.
				baseItem.Items.Add(condition);
			}

			return objectExpression;
		}

		#region IQuery Members

		/// <summary>
		/// Compiles the <see cref="ObjectExpression">ObjectExpression</see> to a
		/// <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see>.
		/// <see cref="Opf3.Query.CompiledQuery">Compiled Queries</see> can only be executed on the
		/// type of storage they have been compiled for.
		/// </summary>
		/// <param name="storage">
		/// Storage that is used to compile the
		/// <see cref="ObjectExpression">ObjectExpression</see>.
		/// </param>
		/// <exception cref="ArgumentNullException">Storage is null.</exception>
		public CompiledQuery ToCompiledQuery(IStorage storage)
		{
			if (storage == null)
				throw new ArgumentNullException("storage");

			// Use the IStorageCommandBuilder to compile this query.
			IStorageCommandBuilder scb = storage.StorageCommandBuilders.GetStorageCommandBuilder(this);
			return scb.ToCompiledQuery(storage, this);
		}

		/// <summary>
		/// Specifies the wait time before terminating the attempt to execute a command and generating an error.
		/// </summary>
		/// <value>An integer that specifies the wait time in seconds.</value>
		/// <exception cref="ArgumentException">Value is lower then zero.</exception>
		public int Timeout
		{
			get { return _timeout; }
			set
			{
				if (value < 0)
					throw new ArgumentException("Value is lower then zero.");

				_timeout = value;
			}
		}

		/// <summary>
		/// Returns the <see cref="Opf3.Query.QueryTypes">QueryType</see> of the ObjectExpression.
		/// </summary>
		/// <value>
		/// A member of the &lt;see cref="Opf3.Query.QueryTypes"&gt;QueryType&lt;/see&gt;
		/// enumeration.
		/// </value>
		public QueryTypes QueryType
		{
			get { return _queryType; }
		}

		#endregion
	}
}
